{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 使用quantOS框架实现双均线策略"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 本段代码利用quantOS系统中事件驱动框架实现了焦炭的双均线策略"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 系统设置"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from __future__ import print_function, division, unicode_literals, absolute_import\n",
    "import time\n",
    "import numpy as np\n",
    "\n",
    "from jaqs.data import RemoteDataService\n",
    "from jaqs.data.basic import Bar, Quote\n",
    "from jaqs.trade import (model, EventLiveTradeInstance, EventBacktestInstance, RealTimeTradeApi,\n",
    "                        EventDrivenStrategy, BacktestTradeApi, PortfolioManager, common)\n",
    "import jaqs.trade.analyze as ana\n",
    "import jaqs.util as jutil"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 登录信息 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "phone = os.environ.get('QUANTOS_USER')\n",
    "token = os.environ.get('QUANTOS_TOKEN')\n",
    "\n",
    "data_config = {\n",
    "  \"remote.data.address\": \"tcp://data.quantos.org:8910\",\n",
    "  \"remote.data.username\": phone,\n",
    "  \"remote.data.password\": token\n",
    "}\n",
    "trade_config = {\n",
    "  \"remote.trade.address\": \"tcp://gw.quantos.org:8901\",\n",
    "  \"remote.trade.username\": phone,\n",
    "  \"remote.trade.password\": token\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 设置结果保存路径"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "result_dir_path = './double_ma'\n",
    "is_backtest = True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 参数设置"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "SYMBOL    = 'j1801.DCE'\n",
    "STARTDATE = 20170602\n",
    "ENDDATE   = 20171020\n",
    "FAST_MA   = 5\n",
    "SLOW_MA   = 15\n",
    "INIT_BALANCE = 100000"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 回测框架定义"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class DoubleMaStrategy(EventDrivenStrategy):\n",
    "    \"\"\"\"\"\"\n",
    "    def __init__(self):\n",
    "        super(DoubleMaStrategy, self).__init__()\n",
    "\n",
    "        # 标的\n",
    "        self.symbol = ''\n",
    "\n",
    "        # 快线和慢线周期\n",
    "        self.fast_ma_len = 0\n",
    "        self.slow_ma_len = 0\n",
    "        \n",
    "        # 记录当前已经过的天数\n",
    "        self.window_count = 0\n",
    "        self.window = 0\n",
    "\n",
    "        # 快线和慢线均值\n",
    "        self.fast_ma = 0\n",
    "        self.slow_ma = 0\n",
    "        \n",
    "        # 固定长度的价格序列\n",
    "        self.price_arr = None\n",
    "\n",
    "        # 当前仓位\n",
    "        self.pos = 0\n",
    "\n",
    "        # 下单量乘数\n",
    "        self.buy_size_unit = 1\n",
    "        self.output = True\n",
    "    \n",
    "    def init_from_config(self, props):\n",
    "        \"\"\"\n",
    "        将props中的用户设置读入\n",
    "        \"\"\"\n",
    "        super(DoubleMaStrategy, self).init_from_config(props)\n",
    "        # 标的\n",
    "        self.symbol = props.get('symbol')\n",
    "\n",
    "        # 初始资金\n",
    "        self.init_balance = props.get('init_balance')\n",
    "\n",
    "        # 快线和慢线均值\n",
    "        self.fast_ma_len = props.get('fast_ma_length')\n",
    "        self.slow_ma_len = props.get('slow_ma_length')\n",
    "        self.window = self.slow_ma_len + 1\n",
    "        \n",
    "        # 固定长度的价格序列\n",
    "        self.price_arr = np.zeros(self.window)\n",
    "\n",
    "    def buy(self, quote, size=1):\n",
    "        \"\"\"\n",
    "        这里传入的'quote'可以是:\n",
    "            - Quote类型 (在实盘/仿真交易和tick级回测中，为tick数据)\n",
    "            - Bar类型 (在bar回测中，为分钟或日数据)\n",
    "        我们通过isinsance()函数判断quote是Quote类型还是Bar类型\n",
    "        \"\"\"\n",
    "        if isinstance(quote, Quote):\n",
    "            # 如果是Quote类型，ref_price为bidprice和askprice的均值\n",
    "            ref_price = (quote.bidprice1 + quote.askprice1) / 2.0\n",
    "        else:\n",
    "            # 否则为bar类型，ref_price为bar的收盘价\n",
    "            ref_price = quote.close\n",
    "            \n",
    "        task_id, msg = self.ctx.trade_api.place_order(quote.symbol, common.ORDER_ACTION.BUY, ref_price, self.buy_size_unit * size)\n",
    "\n",
    "        if (task_id is None) or (task_id == 0):\n",
    "            print(\"place_order FAILED! msg = {}\".format(msg))\n",
    "    \n",
    "    def sell(self, quote, size=1):\n",
    "        if isinstance(quote, Quote):\n",
    "            ref_price = (quote.bidprice1 + quote.askprice1) / 2.0\n",
    "        else:\n",
    "            ref_price = quote.close\n",
    "    \n",
    "        task_id, msg = self.ctx.trade_api.place_order(quote.symbol, common.ORDER_ACTION.SHORT, ref_price, self.buy_size_unit * size)\n",
    "\n",
    "        if (task_id is None) or (task_id == 0):\n",
    "            print(\"place_order FAILED! msg = {}\".format(msg))\n",
    "    \n",
    "    \"\"\"\n",
    "    'on_tick' 接收单个quote变量，而'on_bar'接收多个quote组成的dictionary\n",
    "    'on_tick' 是在tick级回测和实盘/仿真交易中使用，而'on_bar'是在bar回测中使用\n",
    "    \"\"\"\n",
    "    def on_tick(self, quote):\n",
    "        pass\n",
    "\n",
    "    def on_bar(self, quote_dic):\n",
    "        \"\"\"\n",
    "        这里传入的'quote'可以是:\n",
    "            - Quote类型 (在实盘/仿真交易和tick级回测中，为tick数据)\n",
    "            - Bar类型 (在bar回测中，为分钟或日数据)\n",
    "        我们通过isinsance()函数判断quote是Quote类型还是Bar类型\n",
    "        \"\"\"\n",
    "        quote = quote_dic.get(self.symbol)\n",
    "        if isinstance(quote, Quote):\n",
    "            # 如果是Quote类型，mid为bidprice和askprice的均值\n",
    "            bid, ask = quote.bidprice1, quote.askprice1\n",
    "            if bid > 0 and ask > 0:\n",
    "                mid = (quote.bidprice1 + quote.askprice1) / 2.0\n",
    "            else:\n",
    "                # 如果当前价格达到涨停板或跌停板，系统不交易\n",
    "                return\n",
    "        else:\n",
    "            # 如果是Bar类型，mid为Bar的close\n",
    "            mid = quote.close\n",
    "\n",
    "        # 将price_arr序列中的第一个值删除，并将当前mid放入序列末尾\n",
    "        self.price_arr[0: self.window - 1] = self.price_arr[1: self.window]\n",
    "        self.price_arr[-1] = mid\n",
    "        self.window_count += 1\n",
    "\n",
    "        if self.window_count <= self.window:\n",
    "            return\n",
    "\n",
    "        # 计算当前的快线/慢线均值\n",
    "        self.fast_ma = np.mean(self.price_arr[-self.fast_ma_len:])\n",
    "        self.slow_ma = np.mean(self.price_arr[-self.slow_ma_len:])\n",
    "\n",
    "        print(quote)\n",
    "        print(\"Fast MA = {:.2f}     Slow MA = {:.2f}\".format(self.fast_ma, self.slow_ma))\n",
    "\n",
    "        # 交易逻辑：当快线向上穿越慢线且当前没有持仓，则买入1手；当快线向下穿越慢线且当前有持仓，则平仓\n",
    "        if self.fast_ma > self.slow_ma:\n",
    "            if self.pos == 0:\n",
    "                self.buy(quote, 1)\n",
    "            elif self.pos < 0:\n",
    "                self.buy(quote, 2)\n",
    "            else:\n",
    "                pass\n",
    "\n",
    "        elif self.fast_ma < self.slow_ma:\n",
    "            if self.pos == 0:\n",
    "                self.sell(quote, 1)\n",
    "            elif self.pos > 0:\n",
    "                self.sell(quote, 2)\n",
    "            else:\n",
    "                pass\n",
    "                \n",
    "\n",
    "    def on_trade(self, ind):\n",
    "        \"\"\"\n",
    "        交易完成后通过self.ctx.pm.get_pos得到最新仓位并更新self.pos\n",
    "        \"\"\"\n",
    "        print(\"\\nStrategy on trade: \")\n",
    "        print(ind)\n",
    "        self.pos = self.ctx.pm.get_pos(self.symbol)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def run_strategy():\n",
    "    if is_backtest:\n",
    "        \"\"\"\n",
    "        回测模式\n",
    "        \"\"\"\n",
    "        props = {\"symbol\": SYMBOL,\n",
    "#                  \"benchmark\": 'j1801.DCE',\n",
    "                 \"start_date\": STARTDATE,\n",
    "                 \"end_date\": ENDDATE,\n",
    "                 \"fast_ma_length\": FAST_MA,\n",
    "                 \"slow_ma_length\": SLOW_MA,\n",
    "                 \"bar_type\": \"1d\",  # '1d'\n",
    "                 \"init_balance\": INIT_BALANCE}\n",
    "\n",
    "        tapi = BacktestTradeApi()\n",
    "        ins = EventBacktestInstance()\n",
    "        \n",
    "    else:\n",
    "        \"\"\"\n",
    "        实盘/仿真模式\n",
    "        \"\"\"\n",
    "        props = {'symbol': SYMBOL,\n",
    "                 \"fast_ma_length\": FAST_MA,\n",
    "                 \"slow_ma_length\": SLOW_MA,\n",
    "                 'strategy.no': 1062}\n",
    "        tapi = RealTimeTradeApi(trade_config)\n",
    "        ins = EventLiveTradeInstance()\n",
    "\n",
    "    props.update(data_config)\n",
    "    props.update(trade_config)\n",
    "    \n",
    "    ds = RemoteDataService()\n",
    "    strat = DoubleMaStrategy()\n",
    "    pm = PortfolioManager()\n",
    "    \n",
    "    context = model.Context(data_api=ds, trade_api=tapi, instance=ins,\n",
    "                            strategy=strat, pm=pm)\n",
    "    \n",
    "    ins.init_from_config(props)\n",
    "    if not is_backtest:\n",
    "        ds.subscribe(props['symbol'])\n",
    "\n",
    "    ins.run()\n",
    "    if not is_backtest:\n",
    "        time.sleep(9999)\n",
    "    ins.save_results(folder_path=result_dir_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 回测结果分析"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def analyze():\n",
    "    ta = ana.EventAnalyzer()\n",
    "    \n",
    "    ds = RemoteDataService()\n",
    "    ds.init_from_config(data_config)\n",
    "    \n",
    "    ta.initialize(data_server_=ds, file_folder=result_dir_path)\n",
    "    \n",
    "    ta.do_analyze(result_dir=result_dir_path, selected_sec=[])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 执行回测"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "run_strategy()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 执行结果分析 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "analyze()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
